﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace Smartee.RichText.VisitorAndBuilder
{
    public class RtfWriter : IVisitor
    {
        #region IVisitor Members

        #region Handling Document Prolog
        public void StartVisit(Document document)
        {
            _WriteProlog(document.Fonts.ToArray(), document.Colors.ToArray());

            _inTable = false;
        }

        public void EndVisit(Document document)
        {
            _Close();
        }

        public void StartHeader(Document document)
        {
            _writer.WriteLine("{\\*\\header ");
        }

        public void StartFooter(Document document)
        {
            _writer.WriteLine("{\\*\\footer ");
        }

        public void EndHeader(Document document)
        {
            _WriteClosingBrace();
        }

        public void EndFooter(Document document)
        {
            _WriteClosingBrace();
        }
        #endregion

        #region Handling Tables
        public void StartVisit(Table table)
        {
            _writer.Write("\\trowd ");

            _inTable = true;
            _currentTableDirection = table.Direction;

            int totalWidth = 0;
            for (int i = 0; i < table.ColumnsWidth.Length; i++)
                _writer.Write("\\cellx{0} ", totalWidth += table.ColumnsWidth[i].Twips);
        }

        public void StartVisit(Row row)
        {
            if (_currentTableDirection == TextDirection.RightToLeft)
                _writer.Write("\\rtlrow");
            currentCellId = 0;
        }

        public void StartVisit(Cell cell)
        {
            _writer.Write(_GetJustifyString(cell.ParentRow.ParentTable.ColumnsJustification[currentCellId++]));
            _multipleParagraphesInTable = cell.IsMoreThanOneParagraph;
        }

        public void EndVisit(Cell cell)
        {
            _writer.WriteLine("\\cell");
        }

        public void EndVisit(Row row)
        {
            _writer.WriteLine("\\row");
        }

        public void EndVisit(Table table)
        {
            _inTable = false;
            _writer.WriteLine();
        }
        #endregion

        #region Handle Containers
        public void StartVisit(Footnote footnote)
        {
            if (footnote.IsAutoIdentifiedFootnote)
                _StartFootnote();
            else
                _StartFootnote(footnote.Identifier);
        }

        public void EndVisit(Footnote footnote)
        {
            _WriteClosingBrace();
        }

        public void StartVisit(Paragraph paragraph)
        {
            _StartParagraph(paragraph.Justification, paragraph.TextDirection, 0, 0, 0); // TODO RTL
        }

        public void EndVisit(Paragraph paragraph)
        {
            if (_inTable && !_multipleParagraphesInTable)
                _WriteClosingBrace();
            else
                _writer.WriteLine(@"\par}");
        }

        public void Visit(ChangePageNumber changePageNumber)
        {
            _writer.Write("\\pgnstarts{0} ", changePageNumber.NewPageNumber);
        }
        #endregion

        #region Handle Content
        public void Visit(PageBreak pageBreak)
        {
            _writer.WriteLine("\\page");
        }

        public void Visit(NewLine newline)
        {
            _writer.WriteLine(@"\line");
        }

        public void Visit(Text text)
        {
            string content = text.Content.Replace("\r\n", "\n").Replace("\n\r", "\n").Replace("\r", "\n");

            for (int i = 0; i < content.Length; i++)
                if ((int)content[i] > 0x7F)
                    _AddUnicodeChar(content[i]);
                else
                    _AddChar(content[i]);
        }

        public void Visit(PageNumber pageNumber)
        {
            _writer.Write("\\chpgn ");
        }
        #endregion

        #region Handle Formating
        public void Visit(BoldFaceModifier bold)
        {
            _SetCharacterFormatting(CharacterFormatting.Bold, bold.Bold);
        }

        public void Visit(UnderlineModifier underline)
        {
            _SetCharacterFormatting(CharacterFormatting.Underline, underline.Underline);
        }

        public void Visit(StrikeModifier strike)
        {
            _SetCharacterFormatting(CharacterFormatting.Strikethru, strike.Strike);
        }

        public void Visit(FontModifier font)
        {
            _writer.Write(@"\f{0} ", font.FontId);
        }

        public void Visit(ForegoundColorModifier color)
        {
            _writer.Write(@"\cf{0} ", color.ColorId);
        }

        public void Visit(BackgoundColorModifier color)
        {
            _writer.Write(@"\cb{0} ", color.ColorId);
        }

        public void Visit(TextDirectionModifier textDirectionModifier)
        {
            if (textDirectionModifier.TextDirection == TextDirection.RightToLeft)
                _writer.Write("\\rtlch ");
            else
                _writer.Write("\\ltrch ");
        }
        #endregion

        #endregion

        private enum CharacterFormatting { Bold, Underline, Italic, Subscript, Superscript, Smallcaps, Strikethru }
        private enum LanguageCode : ushort { None = 1024, English = 1033, French = 1036, Arabic = 3073, Greek = 1032, Hebrew = 1037 }
        private enum CodePage : ushort { None = 0, English = 1252, French = 1252, Arabic = 1256, Greek = 1253, Hebrew = 1255 }

        private TextWriter _writer;
        private bool _inTable = false;
        private bool _multipleParagraphesInTable = false;
        private int currentCellId = 0;
        private TextDirection _currentTableDirection = TextDirection.LeftToRight;

        public RtfWriter(TextWriter writer)
        {
            _writer = writer;
        }

        #region Handling Footnotes
        /*private void _InsertFootnote(string text)
        {
            _writer.WriteLine(@"{{\super\chftn}}{{\footnote\ftnbj\pard\plain{{\super\chftn}}. {0}}}", text);
        }

        private void _InsertFootnote(string identification, string text)
        {
            _writer.WriteLine(@"{{\super {0}}}{{\footnote\ftnbj\pard\plain{{\super {0}}} {1}}}", identification, text);
        }*/

        private void _StartFootnote()
        {
            _writer.WriteLine(@"{\super\chftn}{\footnote\ftnbj\pard\plain{\super\chftn} ");
        }

        private void _StartFootnote(string identification)
        {
            _writer.WriteLine(@"{{\super {0}}}{{\footnote\ftnbj\pard\plain{{\super {0}}} ", identification);
        }
        #endregion

        #region Handling Formats
        private string _GetJustifyString(Justification justify)
        {
            switch (justify)
            {
                case Justification.Center:
                    return "\\qc";
                case Justification.Full:
                    return "\\qj";
                case Justification.Right:
                    return "\\qr";
                case Justification.Left:
                    return "\\ql";
                case Justification.Nutral:
                    return string.Empty;
                default:
                    throw new ArgumentException();
            }
        }

        private void _SetCharacterFormatting(CharacterFormatting formatting, bool isOn)
        {
            if (isOn)
                _writer.WriteLine(_GetFormattingTag(formatting));
            else
                _writer.WriteLine(_GetFormattingTag(formatting) + "0");
        }

        private void _SetFontSize(int size) // half points
        {
            _writer.WriteLine(@"\fs{0}", size);
        }
        private void _SetColor(int colorId)
        {
            _writer.WriteLine(@"\cf{0}", colorId);
        }

        private void _SetLanguage(LanguageCode code)
        {
            _writer.WriteLine(@"\lang{0}", (ushort)code);
        }

        private string _GetFormattingTag(CharacterFormatting formatting)
        {
            switch (formatting)
            {
                case CharacterFormatting.Bold:
                    return @"\b";
                case CharacterFormatting.Underline:
                    return @"\ul";
                case CharacterFormatting.Italic:
                    return @"\i";
                case CharacterFormatting.Subscript:
                    return @"\sub";
                case CharacterFormatting.Superscript:
                    return @"\super";
                case CharacterFormatting.Smallcaps:
                    return @"\scaps";
                case CharacterFormatting.Strikethru:
                    return @"\strike";
                default:
                    throw new ArgumentException();
            }
        }
        #endregion

        #region Handling Document Definitions
        private void _WriteProlog(FontConfiguration[] fonts, ColorConfiguration[] colors)
        {
            // Document Prolog
            _writer.WriteLine(@"{\rtf1\ansi\deff0{\fonttbl");
            // Fonts table
            for (int i = 0; i < fonts.Length; i++)
                //_writer.WriteLine(@"{{\f{0}\cpg{1} {2};}}", i, (ushort)fonts[i].CodePage, fonts[i].FontName);
                _writer.WriteLine(@"{{\f{0}\f{1} {2};}}", i, fonts[i].FontFamily, fonts[i].FontName);
            // End Font table
            _WriteClosingBrace();

            _writer.Write(@"{\colortbl;");
            for (int i = 0; i < colors.Length; i++)
                _writer.Write(@"\red{0}\green{1}\blue{2};", colors[i].Red, colors[i].Green, colors[i].Blue);
            // End Color Table
            _WriteClosingBrace();

            // Document formatting
            _writer.WriteLine(@"\deflang1033\widowctrl");
        }

        private void _StartColorTable()
        {
            _writer.WriteLine("{\\colortbl\r\n");
        }

        private void _AddColor(int red, int green, int blue)
        {
            _writer.WriteLine("\\red{0} \\green{1} \\blue{2} ;", red, green, blue);
        }

        private void _EndColorTable()
        {
            _writer.WriteLine("}\r\n");
        }
        #endregion

        #region Handling Content
        void _AddChar(char character)
        {
	        if(character == '\\')
		        _writer.Write("\\\\");
	        else if(character == '{')
		        _writer.Write("\\{");
	        else if(character == '}')
		        _writer.Write("\\}");
            else if (character == '\t')
                _AddTab();
            else if (character == '\n')
                _writer.WriteLine(@"\line");
            else if (character == '\xA0')
                _AddHardSpace();
            /*else if(StateLang==CRichWriter::Arabic&&
                            chr>=0x30&&
                            chr<=0x39)
                        {
                            int c = chr-0x30;
                            _writer.Write("\\u16");
                            if(c>7) {Write('4');Write(char(0x28)+c);}
                            else {Write('3');Write(char(0x32)+c);}
                            _writer.Write("? ");
                        }*/
	        else
                _writer.Write(character);
        }
        private void _AddUnicodeChar(char character)
        {
            _writer.Write("\\u{0}?", (int)character);
        }
        private void _AddTab()
        {
            _writer.Write("\\tab ");
        }
        private void _AddHardSpace()
        {
            _writer.Write("\\~ ");
        }
        #endregion

        #region Misc Utilities
        private void _StartParagraph(Justification justify, TextDirection direction, int leftIndenting, int rightIndenting, int firstLineIndenting) // Twips
        {
            if (_inTable)
                _writer.WriteLine(@"{{\pard\intbl{4}\li{0}\ri{1}\fi{2}{3}", leftIndenting, rightIndenting, firstLineIndenting, _GetJustifyString(justify), direction == TextDirection.LeftToRight ? "\\ltrpar" : "\\rtlpar ");
            else
                _writer.WriteLine(@"{{\pard{4}\li{0}\ri{1}\fi{2}{3}", leftIndenting, rightIndenting, firstLineIndenting, _GetJustifyString(justify), direction == TextDirection.LeftToRight ? "\\ltrpar" : "\\rtlpar ");
        }

        private void _WriteClosingBrace()
        {
            _writer.WriteLine("}");
        }

        private void _Close()
        {
            _WriteClosingBrace();
            _writer.Close();
        }
        #endregion
    }
}
